package top.xtcoder.jdcbase.base.service.chunk;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.apache.commons.codec.digest.DigestUtils;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.lang.Lang;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.URLUtil;
import top.xtcoder.jdcbase.base.common.Funs;
import top.xtcoder.jdcbase.base.config.FileUploadConf;
import top.xtcoder.jdcbase.base.core.CreateOp;
import top.xtcoder.jdcbase.base.core.Resp;
import top.xtcoder.jdcbase.base.entity.system.FileChunk;
import top.xtcoder.jdcbase.base.entity.system.FileInfo;
import top.xtcoder.jdcbase.base.redis.RedisCache;
import top.xtcoder.jdcbase.base.utils.BeanUtil;

@Service
public class FileChunkService {
    @Autowired
    private Dao dao;

    @Autowired
	private FileUploadConf conf;
    
    public List<FileChunkDTO> listByFileMd5(String md5) {
        List<FileChunk> fileChunks = dao.query(FileChunk.class, Cnd.where("isdelete", "=", 0).and("md5_identifier", "=", md5));
        if (fileChunks.size() == 0) {
            return Collections.emptyList();
        }
        return BeanUtil.doListToDtoList(fileChunks, FileChunkDTO.class);
    }

    public void saveFileChunk(FileChunkQP param) {
        FileChunk fileChunkDo = null;
        if (!param.isNew()) {
            fileChunkDo = this.dao.fetch(FileChunk.class, param.getId());
            if (null == fileChunkDo) {
                throw new RuntimeException("数据不存在");
            }
        }
        if (null == fileChunkDo) {
            fileChunkDo = new FileChunk();
        } else {
            fileChunkDo.setUpdatetime(new Date());
        }
        BeanUtils.copyProperties(param, fileChunkDo, "id");
        fileChunkDo.setFileName(param.getFilename());
        fileChunkDo.setMd5Identifier(param.getIdentifier());
        int result;
        if (param.isNew()) {
        	fileChunkDo.init();
            result = this.dao.insert(fileChunkDo) != null ? 1 : 0;
        } else {
            result = this.dao.update(fileChunkDo);
        }
        if (0 == result) {
            throw new RuntimeException("操作失败");
        }
        BeanUtil.doToDto(fileChunkDo, FileChunkDTO.class);
    }

    public int deleteById(Long id) {
        int num = this.dao.delete(FileChunk.class, id);
        return num;
    }

    public int delete(FileChunk fileChunk) {
        return this.dao.delete(fileChunk);
    }
    
    /**
     * 分片上传 - 分割上传在合并
     *
     * @param file     : 文件流
     * @param hash     : 哈希值
     * @param filename : 文件名
     * @param seq      : 分片序号
     * @param type     : 文件类型
    */
    public Resp<String> uploadSlice(byte[] file, String hash, String filename, Integer seq, String type) {
        RandomAccessFile raf = null;
        String BASE_DIR = conf.getSavePath() + File.separator;
        try {
            // 创建目标文件夹
            File dir = new File(BASE_DIR + hash);
            if (!dir.exists()) {
                dir.mkdir();
            }
            // 创建空格文件 名称带 seq 用于标识分块信息
            raf = new RandomAccessFile(BASE_DIR + hash + File.separator + Lang.md5(filename) + "." + type + seq, "rw");
            // 写入文件流
            raf.write(file);
        } catch (IOException e) {
            e.printStackTrace();
            return Resp.ERROR("上传失败");
        } finally {
            try {
                if (raf != null) {
                    raf.close();
                }
            } catch (IOException e) {
                // ...打印异常日志...
            }
        }
        return Resp.OK("上传成功");
    }
    
    /**
    * 合并文件的业务代码
    *
    * @param filename : 文件名
    * @param hash     : 文件哈希值
    * @param type     : 文件类型
    */
    public Resp uploadMergeSlice(String filename, String type, String hash) {
        // 判断 hash 对应文件夹是否存在
    	String md5Filename = Lang.md5(filename);
    	String BASE_DIR = conf.getSavePath() + File.separator;
        File dir = new File(BASE_DIR + hash);
        if (!dir.exists()) {
            return Resp.ERROR("合并失败，请稍后重试");
        }
        // 这里通过 FileChannel 来实现信息流复制
        FileChannel out = null;
        // 获取目标 channel
        String savePath = BASE_DIR + File.separator + hash;
        String saveFileName = savePath + File.separator + md5Filename + '.' + type;
        File file = new File(saveFileName);
        try (FileChannel in = new RandomAccessFile(file, "rw").getChannel()) {
            // 分片索引递增
            int index = 1;
            // 开始流位置
            long start = 0;
            while (true) {
                // 分片文件名
                String sliceName = saveFileName + index;
                // 到达最后一个分片 退出循环
                if (!new File(sliceName).exists()) {
                    break;
                }
                // 分片输入流
                out = new RandomAccessFile(sliceName, "r").getChannel();
                // 写入目标 channel
                in.transferFrom(out, start, start + out.size());
                // 位移量调整
                start += out.size();
                out.close();
                out = null;
                FileUtil.del(sliceName);
                // 分片索引调整
                index++;
            }
            // 文件合并完毕
            in.close();
            
            try {
            	filename = URLUtil.decode(filename);
            }catch(Exception e) {
            	
            }
            
            FileInfo fileInfo = new FileInfo();
            fileInfo.init();
            fileInfo.setTitle(filename);
            fileInfo.setName(filename + "." + type);
            fileInfo.setPath(file.getAbsolutePath());
            fileInfo.setContentType(Funs.getFileContentType(file.getAbsolutePath()));
            fileInfo.setFileMd5(DigestUtils.md5Hex(new FileInputStream(file)));
            fileInfo.setSize(FileUtil.size(file));
            fileInfo.setSuffix(type);
            fileInfo.setUrl(conf.getBaseUrl() + "/" + hash + "/" + md5Filename + "." + type);
            if(StpUtil.isLogin()) {
            	fileInfo.setUserid(Funs.getLoginId());
            }else {
            	fileInfo.setUserid(CreateOp.Default.value());
            }
            fileInfo.insertNoInit(dao);
            return Resp.OK("上传成功").data(fileInfo);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return Resp.ERROR("上传失败，请稍后重试");
    }
    
    public Resp fastUploadSlice(String hash) {
        FileInfo fileInfo = dao.fetch(FileInfo.class, Cnd.where("file_md5", "=", hash));
        if (fileInfo != null) {
            return Resp.OK("极速秒传成功", fileInfo);
        } else {
            return Resp.ERROR("极速秒传失败");
        }
    }
}
